Skip to content

feat: data batch processing of number assessment events#2292

Merged
jo-elimu merged 6 commits intomainfrom
2236-add-number-assessment-event
Jul 4, 2025
Merged

feat: data batch processing of number assessment events#2292
jo-elimu merged 6 commits intomainfrom
2236-add-number-assessment-event

Conversation

@jo-elimu
Copy link
Copy Markdown
Member

@jo-elimu jo-elimu commented Jul 4, 2025

Issue Number

Purpose

Technical Details

Testing Instructions

Screenshots


Format Checks

Note

Files in PRs are automatically checked for format violations with mvn spotless:check.

If this PR contains files with format violations, run mvn spotless:apply to fix them.

@jo-elimu jo-elimu self-assigned this Jul 4, 2025
@jo-elimu jo-elimu requested a review from a team as a code owner July 4, 2025 02:26
@jo-elimu jo-elimu requested review from AshishBagdane, shiv810 and vuriaval and removed request for a team July 4, 2025 02:26
@jo-elimu jo-elimu mentioned this pull request Jul 4, 2025
6 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 4, 2025

Warning

Rate limit exceeded

@jo-elimu has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f73eeca and e3abcff.

📒 Files selected for processing (1)
  • src/main/java/ai/elimu/web/analytics/students/NumberAssessmentEventsCsvExportController.java (1 hunks)
## Walkthrough

A new scheduled import task for number assessment events from CSV files was introduced, including a new JPA relationship between NumberAssessmentEvent and Number. Several other event import schedulers had their execution times updated via cron expressions and comments. A new CSV extraction method and corresponding test were added to support number assessment event data parsing.

## Changes

| File(s)                                                                                                    | Change Summary                                                                                                      |
|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| src/main/java/ai/elimu/entity/analytics/NumberAssessmentEvent.java                                         | Added a `@ManyToOne` relationship field `number` to the entity.                                                    |
| src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java                           | Introduced a scheduled service to import number assessment events from CSV files, with deduplication and Discord notifications. |
| src/main/java/ai/elimu/tasks/analytics/WordAssessmentEventImportScheduler.java<br>src/main/java/ai/elimu/tasks/analytics/WordLearningEventImportScheduler.java<br>src/main/java/ai/elimu/tasks/analytics/VideoLearningEventImportScheduler.java<br>src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java | Updated cron expressions and comments to correct scheduled execution times for existing import tasks.              |
| src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java                                          | Added method to extract NumberAssessmentEvent entities from CSV files.                                             |
| src/test/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelperTest.java                                     | Added test method verifying extraction of NumberAssessmentEvent entities from CSV files.                           |
| src/main/java/ai/elimu/web/analytics/MainAnalyticsController.java                                         | Added NumberAssessmentEventDao dependency and included number assessment event count in analytics model.           |
| src/main/java/ai/elimu/web/servlet/CustomDispatcherServlet.java                                           | Added generation and persistence of dummy NumberAssessmentEvent data during DB population for development.         |
| src/main/resources/META-INF/jpa-schema-export.sql                                                        | Added `number_id` column and foreign key constraint to `NumberAssessmentEvent` table referencing `Number` table.    |
| src/main/java/ai/elimu/web/analytics/students/StudentController.java                                      | Added NumberAssessmentEventDao dependency and weekly aggregation of number assessment events for student analytics.|
| src/main/webapp/WEB-INF/jsp/analytics/students/id.jsp                                                    | Added UI section for number assessment events with export button, chart, and recent events table.                  |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Scheduler as NumberAssessmentEventImportScheduler
    participant FileSystem as Local File System
    participant CsvHelper as CsvAnalyticsExtractionHelper
    participant DB as Database
    participant Discord as DiscordService

    Scheduler->>FileSystem: Scan for CSV files in import directory
    loop For each CSV file
        Scheduler->>CsvHelper: Extract NumberAssessmentEvent entities
        loop For each event
            Scheduler->>DB: Check for existing event by timestamp, Android ID, package name
            alt Event does not exist
                Scheduler->>DB: Ensure Student exists (create if needed)
                Scheduler->>DB: Resolve Number entity (if ID present)
                Scheduler->>DB: Persist NumberAssessmentEvent
            end
        end
        Scheduler->>Discord: Post summary message with analytics link
    end
    alt Exception occurs
        Scheduler->>Discord: Notify error
    end

Possibly related PRs

  • feat(event): rest api for number assessment events #2291: The main PR adds a JPA many-to-one relationship field number to the existing NumberAssessmentEvent entity and implements a scheduler to import number assessment events from CSV files, while the retrieved PR introduces the initial NumberAssessmentEvent entity class and a REST API controller for uploading number assessment event CSV files; thus, both PRs modify the same entity class but focus on different aspects—entity enhancement and data import scheduling versus entity creation and REST API—showing a related progression of features around NumberAssessmentEvent.
  • feat: add word assessment events #2210: Adds support for WordAssessmentEvent with similar entity, DAO usage, CSV extraction, import scheduler, and UI components, paralleling the NumberAssessmentEvent features introduced here.

Suggested reviewers

  • jpatel3
  • eymaal
  • alexander-kuruvilla

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=elimu-ai/webapp&utm_content=2292):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java (1)

65-66: Guard against null from listFiles()

File#listFiles() returns null on I/O errors or when the path is not a directory.
Dereferencing without a check risks an NPE that would halt the whole import cycle.

-      for (File analyticsDirFile : analyticsDir.listFiles()) {
+      File[] androidDirs = analyticsDir.listFiles();
+      if (androidDirs == null) {
+        log.warn("Could not list analytics sub-directories in {}", analyticsDir);
+        return;
+      }
+      for (File analyticsDirFile : androidDirs) {
🧹 Nitpick comments (3)
src/main/java/ai/elimu/tasks/analytics/VideoLearningEventImportScheduler.java (1)

54-54: Externalise the cron expression for easier ops tuning

Hard-coding the cron schedule makes future rescheduling require a full redeploy. Consider using a property placeholder so ops can tweak timing without code changes:

-  @Scheduled(cron = "00 50 * * * *") // 50 minutes past every hour
+  // 50 minutes past every hour (default). Override via application properties if needed.
+  @Scheduled(cron = "${analytics.video-learning-event-import.cron:0 50 * * * *}")

This follows the same pattern several of our other schedulers already use and keeps runtime configuration consistent across environments.

src/main/java/ai/elimu/tasks/analytics/WordAssessmentEventImportScheduler.java (1)

54-55: Externalize the cron expression for easier ops tuning

Hard-coding the schedule requires a redeploy for even minor timing tweaks. Move the value to application.properties (e.g. scheduler.wordAssessmentEventImport.cron=0 35 * * * *) and reference it with
@Scheduled(cron = "${scheduler.wordAssessmentEventImport.cron}").
This keeps the code clean while giving ops full control.

src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java (1)

81-88: Delete or archive processed CSVs to cut repeated disk scans

Even though duplicates are skipped at DB level, every execution re-parses the same CSVs, wasting I/O and CPU.
After a successful import, move the file to an imported/ folder (or delete) so subsequent runs only touch new data.
This also simplifies manual troubleshooting.

If desired, I can draft the refactor.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6f30a4 and 0c23220.

📒 Files selected for processing (7)
  • src/main/java/ai/elimu/entity/analytics/NumberAssessmentEvent.java (2 hunks)
  • src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java (1 hunks)
  • src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java (1 hunks)
  • src/main/java/ai/elimu/tasks/analytics/VideoLearningEventImportScheduler.java (1 hunks)
  • src/main/java/ai/elimu/tasks/analytics/WordAssessmentEventImportScheduler.java (1 hunks)
  • src/main/java/ai/elimu/tasks/analytics/WordLearningEventImportScheduler.java (1 hunks)
  • src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
src/main/java/ai/elimu/tasks/analytics/WordLearningEventImportScheduler.java (1)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java (1)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (2)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
Learnt from: jo-elimu
PR: elimu-ai/webapp#2269
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:80-88
Timestamp: 2025-06-20T05:28:00.962Z
Learning: In CsvAnalyticsExtractionHelper.java, the CSV columns for research_experiment and experiment_group contain integer values (0 or 1) that need to be mapped to enum ordinals by adding 1 to the CSV value (0 maps to ordinal 1, 1 maps to ordinal 2).
src/main/java/ai/elimu/tasks/analytics/WordAssessmentEventImportScheduler.java (1)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
src/main/java/ai/elimu/tasks/analytics/VideoLearningEventImportScheduler.java (1)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
🧬 Code Graph Analysis (1)
src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java (2)
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (1)
  • Slf4j (35-624)
src/main/java/ai/elimu/tasks/analytics/WordLearningEventImportScheduler.java (1)
  • Service (45-127)
⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: build (macos-latest, 17)
  • GitHub Check: build (ubuntu-latest, 17)
  • GitHub Check: test_rest
  • GitHub Check: build (ubuntu-latest, 21)
  • GitHub Check: build (windows-latest, 17)
  • GitHub Check: build (windows-latest, 21)
  • GitHub Check: build (macos-latest, 21)
🔇 Additional comments (4)
src/main/java/ai/elimu/tasks/analytics/WordAssessmentEventImportScheduler.java (1)

54-54: Cron time update acknowledged

The new "00 35 * * * *" expression correctly shifts the task to 35 minutes past the hour and matches the inline comment.

src/main/java/ai/elimu/tasks/analytics/WordLearningEventImportScheduler.java (1)

54-54: LGTM!

The scheduling change is correctly implemented with the comment accurately reflecting the new execution time.

src/main/java/ai/elimu/entity/analytics/NumberAssessmentEvent.java (1)

3-5: LGTM!

The JPA relationship is correctly implemented following standard patterns. The placement after the numberId field is logical and maintains consistency with the existing code structure.

Also applies to: 33-34

src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java (1)

45-127: Implementation follows established patterns correctly.

The scheduler implementation is well-structured and consistent with other event import schedulers in the codebase. The error handling, duplicate checking, and Discord notifications are properly implemented.

Comment on lines +54 to 55
@Scheduled(cron = "00 45 * * * *") // 45 minutes past every hour
public synchronized void execute() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Specify a timezone to avoid server-dependent trigger drift

@Scheduled defaults to the JVM default timezone, which varies between environments (local laptop, staging, prod container).
Explicitly set zone = "UTC" (or your canonical TZ) to guarantee the job always fires at exactly 45 min past the hour independent of host configuration.

-  @Scheduled(cron = "00 45 * * * *") // 45 minutes past every hour
+  @Scheduled(cron = "00 45 * * * *", zone = "UTC") // 45 minutes past every UTC hour

#!/bin/bash
set -e

1. Verify that @scheduled is imported from Spring

grep -R "import org.springframework.scheduling.annotation.Scheduled" -n src

2. Check for Spring Boot parent or Spring Framework version in pom.xml

grep -R "spring-boot-starter-parent" -n pom.xml -A2 || echo "No Spring Boot parent found"
grep -R "" -n pom.xml | grep -A2 "springframework" || echo "No direct Spring Framework dependency found"


#!/bin/bash
set -e

1. Confirm that @scheduled is imported in the scheduler class

grep -n "import org.springframework.scheduling.annotation.Scheduled" src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java

2. Check for Spring Boot parent version in pom.xml

grep -n "<spring-boot-starter-parent" -n pom.xml -A1 || echo "No spring-boot-starter-parent found"


Add explicit timezone to @scheduled to avoid trigger drift
Spring’s @scheduled defaults to the JVM’s time zone, which can vary across environments. To ensure this job always runs at 45 minutes past the hour in UTC, add the zone attribute.

• File: src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java (lines 54–55)
Apply this change:

-  @Scheduled(cron = "00 45 * * * *") // 45 minutes past every hour
+  @Scheduled(cron = "00 45 * * * *", zone = "UTC") // 45 minutes past every UTC hour
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Scheduled(cron = "00 45 * * * *") // 45 minutes past every hour
public synchronized void execute() {
@Scheduled(cron = "00 45 * * * *", zone = "UTC") // 45 minutes past every UTC hour
public synchronized void execute() {
🤖 Prompt for AI Agents
In
src/main/java/ai/elimu/tasks/analytics/StoryBookLearningEventImportScheduler.java
at lines 54-55, the @Scheduled annotation lacks an explicit timezone, which can
cause the scheduled task to trigger at different times depending on the JVM's
default timezone. Fix this by adding the zone attribute to the @Scheduled
annotation, setting it to "UTC" to ensure consistent execution at 45 minutes
past every hour regardless of environment.

Comment thread src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java Outdated
log.info("csvFile: " + csvFile);

// Convert from CSV to Java
List<NumberAssessmentEvent> events = CsvAnalyticsExtractionHelper.extractNumberAssessmentEvents(csvFile);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Method extractNumberAssessmentEvents is not implemented.

The method CsvAnalyticsExtractionHelper.extractNumberAssessmentEvents called here doesn't exist in the helper class, which will cause a compilation error.

Let me verify if this method exists elsewhere in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for the extractNumberAssessmentEvents method definition
ast-grep --pattern 'extractNumberAssessmentEvents($$$) {
  $$$
}'

# Also search with ripgrep in case the pattern doesn't match
rg -A 5 "extractNumberAssessmentEvents"

Length of output: 955


Implement the missing extractNumberAssessmentEvents method

The call at

  • src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java:77
    will fail to compile because CsvAnalyticsExtractionHelper.extractNumberAssessmentEvents does not exist.

Action items:

  • Add a new method in src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:
    public static List<NumberAssessmentEvent> extractNumberAssessmentEvents(File csvFile) throws IOException {
        // parse the CSV (similar to other extractXXX methods)
        // map rows to NumberAssessmentEvent instances
    }
  • Use the existing extraction methods (e.g. extractWordLearningEvents, extractLetterSoundLearningEvents) as a template for CSV parsing logic and error handling.
🤖 Prompt for AI Agents
In
src/main/java/ai/elimu/tasks/analytics/NumberAssessmentEventImportScheduler.java
at line 77, the call to
CsvAnalyticsExtractionHelper.extractNumberAssessmentEvents fails because the
method does not exist. To fix this, add a new static method named
extractNumberAssessmentEvents in
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java that accepts a
File parameter and returns a List of NumberAssessmentEvent. Implement CSV
parsing logic similar to existing methods like extractWordLearningEvents,
mapping CSV rows to NumberAssessmentEvent objects, and include appropriate error
handling such as throwing IOException.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jul 4, 2025

Codecov Report

Attention: Patch coverage is 24.71264% with 131 lines in your changes missing coverage. Please review.

Project coverage is 16.57%. Comparing base (d8ac68c) to head (e3abcff).
Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
...nalytics/NumberAssessmentEventImportScheduler.java 2.08% 47 Missing ⚠️
...nts/NumberAssessmentEventsCsvExportController.java 2.50% 39 Missing ⚠️
...limu/web/analytics/students/StudentController.java 0.00% 22 Missing ⚠️
.../ai/elimu/web/servlet/CustomDispatcherServlet.java 0.00% 17 Missing ⚠️
...i/elimu/util/csv/CsvAnalyticsExtractionHelper.java 89.13% 4 Missing and 1 partial ⚠️
...i/elimu/web/analytics/MainAnalyticsController.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2292      +/-   ##
============================================
+ Coverage     16.37%   16.57%   +0.19%     
- Complexity      454      459       +5     
============================================
  Files           257      259       +2     
  Lines          7546     7718     +172     
  Branches        872      894      +22     
============================================
+ Hits           1236     1279      +43     
- Misses         6242     6370     +128     
- Partials         68       69       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c23220 and 07d1858.

⛔ Files ignored due to path filters (1)
  • src/test/resources/ai/elimu/util/csv/5b7c682a12ecbe2e_4000028_number-assessment-events_2025-07-03.csv is excluded by !**/*.csv, !**/*.csv
📒 Files selected for processing (3)
  • pom-dependency-tree.txt (1 hunks)
  • src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (2 hunks)
  • src/test/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelperTest.java (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pom-dependency-tree.txt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (4)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2269
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:80-88
Timestamp: 2025-06-20T05:28:00.962Z
Learning: In CsvAnalyticsExtractionHelper.java, the CSV columns for research_experiment and experiment_group contain integer values (0 or 1) that need to be mapped to enum ordinals by adding 1 to the CSV value (0 maps to ordinal 1, 1 maps to ordinal 2).
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
Learnt from: venkatesh2k3
PR: elimu-ai/webapp#1682
File: src/main/java/ai/elimu/dao/LetterSoundContributionEventDao.java:13-13
Timestamp: 2024-07-06T17:37:44.413Z
Learning: In the `LetterSoundContributionEventDao.java` file, the method signatures should use `LetterSoundContributionEvent` instead of `LetterSoundCorrespondenceContributionEvent` to maintain consistency with the renaming of entities.
Learnt from: jo-elimu
PR: elimu-ai/webapp#0
File: :0-0
Timestamp: 2025-05-03T08:01:30.217Z
Learning: The elimu-ai/webapp repository has a dependency on ai.elimu:model (version 2.0.97) that provides shared model classes and enums. The Language enum should be imported from ai.elimu.model.v2.enums.Language instead of creating local duplicates.
src/test/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelperTest.java (3)
Learnt from: jo-elimu
PR: elimu-ai/webapp#2269
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:80-88
Timestamp: 2025-06-20T05:28:00.962Z
Learning: In CsvAnalyticsExtractionHelper.java, the CSV columns for research_experiment and experiment_group contain integer values (0 or 1) that need to be mapped to enum ordinals by adding 1 to the CSV value (0 maps to ordinal 1, 1 maps to ordinal 2).
Learnt from: jo-elimu
PR: elimu-ai/webapp#2237
File: src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java:287-288
Timestamp: 2025-06-09T06:52:22.910Z
Learning: VideoLearningEvents in CsvAnalyticsExtractionHelper never used the "time" column name and always use "timestamp", unlike other event types (LetterSoundLearningEvent, WordAssessmentEvent, WordLearningEvent, StoryBookLearningEvent) which need conditional logic to handle both "time" (for versions < 3.4.0) and "timestamp" (for versions >= 3.4.0) column names. This is because VideoLearningEvents were introduced after the schema change.
Learnt from: venkatesh2k3
PR: elimu-ai/webapp#1682
File: src/main/java/ai/elimu/dao/LetterSoundContributionEventDao.java:13-13
Timestamp: 2024-07-06T17:37:44.413Z
Learning: In the `LetterSoundContributionEventDao.java` file, the method signatures should use `LetterSoundContributionEvent` instead of `LetterSoundCorrespondenceContributionEvent` to maintain consistency with the renaming of entities.
🧬 Code Graph Analysis (1)
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (1)
src/main/java/ai/elimu/util/AnalyticsHelper.java (1)
  • AnalyticsHelper (6-43)
⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: test_rest
  • GitHub Check: build (windows-latest, 21)
  • GitHub Check: build (windows-latest, 17)
  • GitHub Check: build (ubuntu-latest, 21)
  • GitHub Check: build (ubuntu-latest, 17)
  • GitHub Check: build (macos-latest, 17)
  • GitHub Check: build (macos-latest, 21)
🔇 Additional comments (4)
src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java (1)

5-5: LGTM! Import correctly added.

The import for NumberAssessmentEvent is properly placed in alphabetical order and is required for the new extraction method.

src/test/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelperTest.java (3)

5-5: LGTM: Import additions are appropriate.

The new imports for assertTrue, JSONObject, and NumberAssessmentEvent are correctly added and necessary for the new test method functionality.

Also applies to: 11-11, 19-19


220-220: TODO comment is consistent with class structure.

The TODO comment for "number learning events" maintains consistency with the existing TODO structure at line 30 for "letter-sound assessment events."


192-218: Confirm existence and content of the test CSV resource

I wasn’t able to locate the file
src/test/resources/ai/elimu/util/csv/5b7c682a12ecbe2e_4000028_number-assessment-events_2025-07-03.csv
in the cloned repo. Please make sure this CSV is checked in at the expected path and that its header + first data row match the test’s expectations:

  • numberSelected column = 6
  • numberValue column = 9
  • numberId column is empty/null

Once you confirm the file exists and those values align, the test’s assertions (including getNumberId() == null) will be valid.

Comment on lines +202 to +274
public static List<NumberAssessmentEvent> extractNumberAssessmentEvents(File csvFile) {
log.info("extractNumberAssessmentEvents");

Integer versionCode = AnalyticsHelper.extractVersionCodeFromCsvFilename(csvFile.getName());
log.info("versionCode: " + versionCode);

List<NumberAssessmentEvent> numberAssessmentEvents = new ArrayList<>();

// Iterate each row in the CSV file
Path csvFilePath = Paths.get(csvFile.toURI());
log.info("csvFilePath: " + csvFilePath);
try {
Reader reader = Files.newBufferedReader(csvFilePath);
CSVFormat csvFormat = CSVFormat.DEFAULT.withFirstRecordAsHeader();
log.info("header: " + Arrays.toString(csvFormat.getHeader()));
CSVParser csvParser = new CSVParser(reader, csvFormat);
for (CSVRecord csvRecord : csvParser) {
log.info("csvRecord: " + csvRecord);

// Convert from CSV to Java

NumberAssessmentEvent numberAssessmentEvent = new NumberAssessmentEvent();

long timestampInMillis = Long.valueOf(csvRecord.get("timestamp").substring(0, 10)) * 1_000;
Calendar timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
timestamp.setTimeInMillis(timestampInMillis);
numberAssessmentEvent.setTimestamp(timestamp);

String androidId = AnalyticsHelper.extractAndroidIdFromCsvFilename(csvFile.getName());
numberAssessmentEvent.setAndroidId(androidId);

String packageName = csvRecord.get("package_name");
numberAssessmentEvent.setPackageName(packageName);

Float masteryScore = Float.valueOf(csvRecord.get("mastery_score"));
numberAssessmentEvent.setMasteryScore(masteryScore);

Long timeSpentMs = Long.valueOf(csvRecord.get("time_spent_ms"));
numberAssessmentEvent.setTimeSpentMs(timeSpentMs);

String additionalData = csvRecord.get("additional_data");
if (StringUtils.isNotBlank(additionalData)) {
numberAssessmentEvent.setAdditionalData(additionalData);
}

int researchExperimentOrdinal = Integer.valueOf(csvRecord.get("research_experiment"));
ResearchExperiment researchExperiment = ResearchExperiment.values()[researchExperimentOrdinal];
numberAssessmentEvent.setResearchExperiment(researchExperiment);

int experimentGroupOrdinal = Integer.valueOf(csvRecord.get("experiment_group"));
ExperimentGroup experimentGroup = ExperimentGroup.values()[experimentGroupOrdinal];
numberAssessmentEvent.setExperimentGroup(experimentGroup);

Integer numberValue = Integer.valueOf(csvRecord.get("number_value"));
numberAssessmentEvent.setNumberValue(numberValue);

// String numberSymbol = csvRecord.get("number_symbol");
// numberAssessmentEvent.setNumberSymbol(numberSymbol);

if (StringUtils.isNotBlank(csvRecord.get("number_id"))) {
Long numberId = Long.valueOf(csvRecord.get("number_id"));
numberAssessmentEvent.setNumberId(numberId);
}

numberAssessmentEvents.add(numberAssessmentEvent);
}
csvParser.close();
} catch (IOException ex) {
log.error(ex.getMessage());
}

return numberAssessmentEvents;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix critical inconsistencies with established assessment event extraction patterns.

The implementation has several issues that make it inconsistent with other assessment event extractors in this class:

  1. Missing version-based timestamp handling (line 225) - should use conditional logic like other assessment events
  2. Missing version check for additionalData (lines 242-245) - should check versionCode >= 3006000
  3. Missing version check and null validation for research experiments (lines 247-253) - should check versionCode >= 3005009 and use StringUtils.isNotBlank()
  4. Unexplained commented code (lines 258-259) - numberSymbol field is commented without explanation

Apply this diff to fix the inconsistencies and align with the extractWordAssessmentEvents pattern:

-                long timestampInMillis = Long.valueOf(csvRecord.get("timestamp").substring(0, 10)) * 1_000;
+                String timestampColumnName = null;
+                if (versionCode < 3004000) {
+                    timestampColumnName = "time";
+                } else {
+                    // https://github.com/elimu-ai/analytics/releases/tag/3.4.0
+                    timestampColumnName = "timestamp";
+                }
+                long timestampInMillis = Long.valueOf(csvRecord.get(timestampColumnName).substring(0, 10)) * 1_000;
-                String additionalData = csvRecord.get("additional_data");
-                if (StringUtils.isNotBlank(additionalData)) {
-                    numberAssessmentEvent.setAdditionalData(additionalData);
-                }
+                if (versionCode >= 3006000) {
+                    // https://github.com/elimu-ai/analytics/releases/tag/3.6.0
+                    String additionalData = csvRecord.get("additional_data");
+                    if (StringUtils.isNotBlank(additionalData)) {
+                        numberAssessmentEvent.setAdditionalData(additionalData);
+                    }
+                }
-                int researchExperimentOrdinal = Integer.valueOf(csvRecord.get("research_experiment"));
-                ResearchExperiment researchExperiment = ResearchExperiment.values()[researchExperimentOrdinal];
-                numberAssessmentEvent.setResearchExperiment(researchExperiment);
-
-                int experimentGroupOrdinal = Integer.valueOf(csvRecord.get("experiment_group"));
-                ExperimentGroup experimentGroup = ExperimentGroup.values()[experimentGroupOrdinal];
-                numberAssessmentEvent.setExperimentGroup(experimentGroup);
+                if (versionCode >= 3005009) {
+                    // https://github.com/elimu-ai/analytics/releases/tag/3.5.9
+                    
+                    if (StringUtils.isNotBlank(csvRecord.get("research_experiment"))) {
+                        int researchExperimentOrdinal = Integer.valueOf(csvRecord.get("research_experiment"));
+                        ResearchExperiment researchExperiment = ResearchExperiment.values()[researchExperimentOrdinal];
+                        numberAssessmentEvent.setResearchExperiment(researchExperiment);
+                    }
+
+                    if (StringUtils.isNotBlank(csvRecord.get("experiment_group"))) {
+                        int experimentGroupOrdinal = Integer.valueOf(csvRecord.get("experiment_group"));
+                        ExperimentGroup experimentGroup = ExperimentGroup.values()[experimentGroupOrdinal];
+                        numberAssessmentEvent.setExperimentGroup(experimentGroup);
+                    }
+                }
-                // String numberSymbol = csvRecord.get("number_symbol");
-                // numberAssessmentEvent.setNumberSymbol(numberSymbol);
+                String numberSymbol = csvRecord.get("number_symbol");
+                numberAssessmentEvent.setNumberSymbol(numberSymbol);
🤖 Prompt for AI Agents
In src/main/java/ai/elimu/util/csv/CsvAnalyticsExtractionHelper.java between
lines 202 and 274, fix inconsistencies by adding version-based conditional logic
for timestamp extraction similar to other assessment event extractors, ensure
additionalData is only set if versionCode is at least 3006000, add a versionCode
check (>= 3005009) and use StringUtils.isNotBlank() before setting
researchExperiment, and remove or properly explain the commented-out
numberSymbol code to maintain clarity and consistency.

@jo-elimu jo-elimu merged commit 780edbe into main Jul 4, 2025
13 checks passed
@jo-elimu jo-elimu deleted the 2236-add-number-assessment-event branch July 4, 2025 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant